package com.sun.mongodb.core.query;

import cn.hutool.core.collection.CollectionUtil;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.sun.mongodb.core.params.LookUpParam;
import com.sun.mongodb.core.reflection.ReflectionUtil;
import com.sun.mongodb.core.reflection.SerializableFunction;
import com.sun.mongodb.core.util.SpringContextHolder;
import org.bson.Document;
import org.springframework.data.domain.Sort;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.aggregation.Aggregation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
import org.springframework.data.mongodb.core.query.CriteriaDefinition;

import java.util.ArrayList;
import java.util.List;

/**
 * 聚合函数  查询条件
 */
public class AggregationWrapper {

    List<AggregationOperation>  operations = new ArrayList<AggregationOperation>();

    public Aggregation buildAggregation(){
        Aggregation aggregation = Aggregation.newAggregation(operations);
        return aggregation;
    }

    /**
     *  添加 Aggregation 操作
     * @param operation
     * @return
     */
    public AggregationWrapper add(AggregationOperation operation) {
        this.operations.add(operation);
        return this;
    }

    /**
     * $match  匹配查询   不能使用空间地理符  及分页 排序
     *
     * @param criteria
     * @return
     */
    public AggregationWrapper match(CriteriaWrapper criteria) {
        if(criteria!=null){
            this.operations.add(Aggregation.match(criteria.buildCriteria()));
        }
        return this;
    }


    /**
     * $skip  跳过数量
     *
     * @param num
     * @return
     */
    public AggregationWrapper skip(long num) {
        this.operations.add(Aggregation.skip(num) );
        return this;
    }
    /**
     * $limit    查询数量
     *
     * @param num
     * @return
     */
    public AggregationWrapper limit(long num) {
        this.operations.add(Aggregation.limit(num) );
        return this;
    }


    /**
     * $sample   随机返回
     * @param num  随机数量
     * @return
     */
    public AggregationWrapper sample(long num) {
        this.operations.add(Aggregation.sample(num) );
        return this;
    }

    /**
     * $lookup  表关联查询
     * @param joinfrom  关联表-表名
     * @param localField  主表-字段名
     * @param foreignField 关联表-字段名
     * @param as  别名
     * @return
     */
    public AggregationWrapper lookup(String joinfrom, String localField, String foreignField, String as) {
        this.operations.add(Aggregation.lookup(joinfrom,localField,foreignField,as) );
        return this;
    }

    /**
     * $lookup  表关联查询
     * @param clazz  关联表-类
     * @param localField  主表-字段名
     * @param foreignField 关联表-字段名
     * @param as  别名
     * @return
     */
    public <E, R> AggregationWrapper lookup(Class<?> clazz,SerializableFunction<E, R> localField,SerializableFunction<E, R> foreignField,SerializableFunction<E, R> as ) {
        this.operations.add( Aggregation.lookup( getCollectionName(clazz), getFieldName(localField), getFieldName(foreignField), getFieldName(as) ));
        return this;
    }

    /**
     * $lookup  表关联查询
     * @param param
     * @return
     */
    public <E, R> AggregationWrapper lookup(LookUpParam param) {
        if(null!=param){
            param.check();
            this.operations.add( param.buildAggregationOperation());
        }
        return this;
    }
    /**
     * $sort   排序
     * @param criteria
     * @return
     */
    public AggregationWrapper sort(CriteriaWrapper criteria){
        Sort sort =criteria.buildSort();
        if(null!=sort){
            this.operations.add(Aggregation.sort(sort) );
        }
        return this;
    }


    /**
     * $group   分组
     * @return
     */
    public <E, R> AggregationWrapper group(SerializableFunction<E, R>... columns){
        int len = columns.length;
        String[] columnArr = new String[len];
        for(int x=0;x<len;x++){
            columnArr[x] = getFieldName(columns[x]);
        }
        this.operations.add(Aggregation.group(columnArr));
        return this;
    }

    /**
     * $unwind   解构文档中的数组字段
     * @return
     */
    public <E, R> AggregationWrapper unwind(SerializableFunction<E, R> column){
        this.operations.add(Aggregation.unwind( ReflectionUtil.getFieldName(column)));
        return this;
    }


    /**
     * 聚合管道函数  $graphLookup  图查询   对集合执行递归搜索。
     * @param from    collection 表名
     * @param startWith
     * @param connectFrom  主表字段-字段名
     * @param connectTo     关联字段名
     * @param as     别名
     * @param maxDepth   递归深度
     * @param depthField  深度字段
     * @param restrictSearchWithMatch  “递归”条件
     * @return
     */
    public AggregationWrapper graphLookup(String from, List<Object> startWith, String connectFrom, String connectTo, String as, Long maxDepth, String depthField, CriteriaDefinition restrictSearchWithMatch) {
        AggregationOperation aggregationOperation = new AggregationOperation() {
            public Document toDocument(AggregationOperationContext aggregationOperationContext) {
                DBObject graphLookup = new BasicDBObject("from", from)
                        .append("connectFromField", connectFrom)
                        .append("connectToField", connectTo)
                        .append("as", as)
                     ;
                if(CollectionUtil.isNotEmpty(startWith)){
                    graphLookup.put("startWith", startWith);
                }else{
                    graphLookup.put("startWith", "$" + connectFrom);
                }
                if(maxDepth!=null){
                    graphLookup.put("maxDepth", maxDepth);
                }
                if(restrictSearchWithMatch!=null) {
                    graphLookup.put("restrictSearchWithMatch", restrictSearchWithMatch);
                }
                if(depthField!=null) {
                    graphLookup.put("depthField", depthField);
                }
                return new Document("$graphLookup", graphLookup);
            }
        };
        this.operations.add(aggregationOperation);
        return  this;
    }



    private <E,R> String getFieldName(SerializableFunction<E,R> func){
        return  ReflectionUtil.getFieldName(func);
    }
    private String getCollectionName(Class<?> clazz){
        return  SpringContextHolder.getBean(MongoTemplate.class).getCollectionName(clazz);
    }
}
